package mars.ns.visualisation

import mars.ns.core.Node
import mars.ns.core.Network
import mars.ns.core.LinkType
import mars.ns.core.Link
import mars.ns.core.Edge
import prefuse.Display
import prefuse.demos.GraphView
import prefuse.data.Graph
import prefuse.util.GraphLib
import javax.swing.JFrame
import prefuse.util.ui.UILib
import prefuse.data.EdgeVis
import prefuse.data.NodeVis
import scala.collection.mutable.Map
import java.awt.Color
import mars.ns.HasState

/**
 * Visualizer using Prefuse, a Framework to draw a graph with nodes and edges
 * @see	prefuse.jar
 */
class PrefuseNetworkVisualizer[T <: Node](network : Network[T]) extends NetworkVisualizer(network){
    private var frame : JFrame = null
    private var graph : Graph = new Graph
    private var graphView : GraphView = null
    private var addedEdges : Map[String,EdgeVis] = Map[String,EdgeVis]()
    private var addedNodes : Map[Int,NodeVis] = Map[Int,NodeVis]()
    private var linkType : LinkType = null
    final val GREY : Color = new Color(200,200,255);
    final val ORANGE : Color = new Color(200,50,20);
    final val GREEN : Color = new Color(60,200,20);
    
	def visualise{
        this.visualise(null)
    }
	
    def visualise(linkType : LinkType) {
        this.linkType = linkType
	    UILib.setPlatformLookAndFeel();
        this.frame = new JFrame("p r e f u s e  |  g r a p h v i e w")
        createGraph
        this.graphView = new GraphView(this.graph,"label")
        this.addedNodes map {case (_,no) => markNode(no , this.GREY) }
    	this.frame.setContentPane(this.graphView)
        this.frame.pack();
        this.frame.setVisible(true);
        // frame.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE)
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    }

    /**
     * Closes the visualisationFrame
     */
    def stopVisualisation {
        this.frame.dispose();
    }
    
    /**
     * Creates a graph using the getNodes method
     */
    private def createGraph {
        this.graph = new Graph
		this.addedNodes = Map[Int,NodeVis]()
        this.graph.getNodeTable().addColumns(GraphLib.LABEL_SCHEMA)
        addNetworkToGraph
    }
    
    /**
     *	Adding all the nodes and edges from the network to the graph 
     */
    private def addNetworkToGraph{
        network.getNodes map {node => addNode(node)}
        network.getNodes map {nodeFrom => nodeFrom.getLinks map {link => addEdge(nodeFrom,link.getLinkedTo(),link.getType())}}
    }
	
    /**
     * @return	True if the given node is already added to this visualisation
     */
    private def isAdded(node : Node) : Boolean = {
       node != null && this.addedNodes.contains(node.id)
    }
       
    private def getVisual(node : Node) : NodeVis =  {
		if(node != null)
			addedNodes(node.id)   
		else
		    null
    }

    private def updateVisualisation(){
        if(this.graphView != null)
            this.graphView.runDraw()
    }

    /**
     * Gives a node a different color: ORANGE
     */
    def markNode(node : Node) : Boolean = {
    	return markNode(node,this.GREEN)   
    }

    /**
     * Gives a node the given color
     */
    def markNode(node : Node, color : Color) : Boolean = {
    	if(isAdded(node) && color != null && this.graphView != null){
    	   return markNode(getVisual(node),color)
    	}
    	return false
    }
    
    private def markNode(node : NodeVis , color : Color) : Boolean = {
        if(node != null && color != null && this.graphView != null){
    	   this.graphView.markNode(node, color)
    	   return true
    	}
    	return false
    }    
    
    /**
     * Gives a node the given name
     */
    def setNodeName(node : Node, name : String) : Boolean = {
        if(isAdded(node) && name != null){
            getVisual(node).setString(GraphLib.LABEL, name)
            return true
    	}
    	return false        
    }
    
    /**
     * Adds a node to the visualisation if it's not already added
     * @return	True if success
     */
    def addNode(node : Node) : Boolean = {
        if(node == null)
            throw new Exception("Can't add node null in Visualizer")        
        if(!isAdded(node)){
    		addedNodes(node.id) = this.graph.addNode()
			setNodeName(node,node.toString)
			markNode(node, this.GREY);
			updateVisualisation
    		return true
        }
    	return false
    }
    
    /**
     * removes a node from the visualisation, also removes all links
     * @return True if removed
     */
    def removeNode(node : Node) : Boolean =
    {
        if(node == null)
            throw new Exception("Can't remove node null in Visualizer")
        if(isAdded(node) && this.graph.removeNode(getVisual(node))){
            addedNodes.removeKey(node.id)
            updateVisualisation
            return true
        }
        return false
    }

    /**
     *	Adds an edge to the visualizer
     *	Only edges with LinkTypes that are instances of the given linkType will be added
     *	if(linktype == null) all edges will be added
     */
    def addEdge(from : Node, to : Node, edgeLinkType : LinkType) : Boolean = {
        if(from == null || to == null || edgeLinkType == null)
            throw new Exception("Can't add edge with null value in Visualizer")
        if(this.linkType == null || edgeLinkType.getClass().isInstance(this.linkType))
        {
    		val edge : Edge = new Edge(from,to,edgeLinkType)
            if(this.addedEdges contains edge.uniqueString)
                return false
            this.addNode(from)
            this.addNode(to)
	    	this.addedEdges(edge.uniqueString) = this.graph.addEdge(getVisual(from),getVisual(to))
	    	updateVisualisation
	    	return true
    	}
        return false
    }
    
    /**
     * removes a link from the visualisation
     */
    def removeEdge(from : Node, to : Node, edgeLinkType : LinkType) : Boolean = {
		if(from == null || to == null || edgeLinkType == null)
            throw new Exception("Can't remove edge null in Visualizer")
        val edge : Edge = new Edge(from,to,edgeLinkType)
        if(this.graph.removeEdge(addedEdges(edge.uniqueString))){
        	addedEdges.removeKey(edge.uniqueString)
        	updateVisualisation
            return true
        }
        return false
    }
}